package connections;



import java.io.IOException;
import java.util.Vector;

import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DataElement;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;
import javax.microedition.lcdui.TextField;



/*
 * Created on Feb 16, 2005
 *
 */

/**
 * @author Dan O'Sullivan
 * 
 * A wrapper around some of the bluetooth J2ME classes to hide some of the specifics of bluetooth device and service discovery and connection if you just want to make a serial port connection. Works in connection with classes that have implemented the BTConnectionListener to alert them about events like connections and discoveries (device and service)
 */

public class BTSerialConnection extends Connection implements DiscoveryListener, BTConnectionListener {
	Vector deviceList;

	Vector serviceList;

	Vector devicesFound = null;

	Vector servicesFound = null;

	DiscoveryAgent agent = null;

	boolean foundDevice = false;

	int whichServiceSearch;

	TextField btID;

	String url = "URL:";

	boolean connected;

	BTConnectionListener btProgram;

	StreamConnection connection;

	String friendlyName = "none";
	
	BTServer myBTServer;
	

	/**
	 * If you know the hardware address for the bluetooth device you want to talk to, ie you don't need to go through the process of discovery.
	 * 
	 * @param _mainProgram
	 *            Object you want to be notified when a connection is made or that data has come in. It needs to implement at least CnectionListener and maybe also BlueToothListener.
	 * @param _btAddressForDirect
	 *            Hardware address for bluetooth device you want to connect with. It might look something like this something like 00F8F898390:1. I think the :1 or sometimes :01 is to signify the service, in this case serial connection. If you don't know this go through a discovery process first to learn it.
	 */
	public BTSerialConnection(ConnectionListener _mainProgram, String _btAddressForDirect) {
		btProgram = this;
		mainProgram = _mainProgram;
		address = _btAddressForDirect;
		try {
			LocalDevice local = LocalDevice.getLocalDevice();
		} catch (BluetoothStateException bse) {
			//mainProgram.feedback(this, "couldn't get local device");
			System.out.println("No local device");
		}
		System.out.println("Connect to add" + _btAddressForDirect);
		connect(address);
	}

	/**
	 * If you don't know the hardware address for the bluetooth device you want to talk to. You can later use this object to discover it.
	 * 
	 * @param _mainProgram
	 *            Object you want to be notified when a connection is made or that data has come in. It needs to implement at least ConnectionListener and probably a BlueToothListener so you can recieve the alerts about device and serice discovery.
	 */
	public BTSerialConnection(ConnectionListener _mainProgram) {
		btProgram = this;
		mainProgram = _mainProgram;
	}

	/**
	 * Sets who should be notified if a device or service is found.
	 * 
	 * @param The
	 *            object that you want to be notified if there is a Bluetooth discovery event.
	 */
	public void addBlueToothListener(BTConnectionListener _btl) {
		btProgram = _btl;
	}

	/**
	 * Connects to a known address.
	 * 
	 * @param _address
	 *            Hardware address and service. Should be something like 00GH898883:1 or :01
	 */
	public void connect(String _address) {
		if (mainProgram == null) mainProgram = this; // if they don't want to hear back listen here.
		if (_address != null) {
			if (address.toLowerCase().equals("wait")) {
				// BTServer server = new BTServer(mainProgram, waitForBytes);
				// server.run_server();
				// mainForm.append("Waiting for connection.");
			} else {
				connectToHardwareAddress(_address);
			}
		}

	}

	/**
	 * Initiates the process of device disovery. It takes some time. A BTConnectionListener will be alerted with a passing of a list of devices to the funciton devicesAvailable when it finishes.
	 */
	public void doDeviceDiscovery() {

		LocalDevice local = null;

		try {
			local = LocalDevice.getLocalDevice();
			agent = local.getDiscoveryAgent();
		} catch (BluetoothStateException bse) {
			mainProgram.feedback(this, "couldn't get local device");
			System.out.println("No local device");
		}
		

		devicesFound = new Vector();
		deviceList = new Vector();

		try {
			if (!agent.startInquiry(DiscoveryAgent.GIAC, this)) {
				mainProgram.feedback(this, "Inquirey not started");
			}
		} catch (BluetoothStateException bse) {
			mainProgram.feedback(this, "Inquirey error" + bse);

		}
	}

	public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass deviceClass) {
		try {
			String who = remoteDevice.getFriendlyName(false);
			if (who == null) who = remoteDevice.getBluetoothAddress();
			//System.out.println("Send found device to MainProgram " + mainProgram.getClass());
			btProgram.feedback(this, "Found " + who + "\n");

			devicesFound.addElement(remoteDevice);
			deviceList.addElement(who);

			// if you know what you want just cut to the chase??????????????????;

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	public void inquiryCompleted(int param) {

		switch (param) {
		case DiscoveryListener.INQUIRY_COMPLETED: {
			mainProgram.feedback(this, "Discovery complete.");
			btProgram.devicesAvailable(deviceList);

			break;
		}
		case DiscoveryListener.INQUIRY_ERROR:
			mainProgram.feedback(this, "INQUIRY ERROR");
			break;
		case DiscoveryListener.INQUIRY_TERMINATED:
			mainProgram.feedback(this, "INQUIRY TERMINATED");
			break;
		}
	}

  public	void waitForConnection() {
	  
	  
		myBTServer = new BTServer();
		mainProgram.feedback(this, "Waiting for Connection");

	  
	}

	/**
	 * Tacks the other parts of the "url" on to the hardware adress.
	 * 
	 * @param _address
	 *            Hardware address and service. Should be something like 00GH898883:1 or :01
	 */
	public void connectToHardwareAddress(String _address) {
	
		address = _address;
		// if address does not have a colon do add 01
		if (_address.indexOf(":") == -1) _address = address + ":01";
		String url = "btspp://" + _address + ";authenticate=false;encrypt=false;master=false";
		connectToURL(url);
	}

	public void disconnect() {
		super.disconnect();
		try {

			if (connection != null) connection.close();

		} catch (IOException e) {
			System.out.println("Never Opened");
		}
	}

	/**
	 * Connects to a known url. When it succeeds it notifies the ConnectionListener of a connection.
	 * 
	 * @param _url
	 *            url is a little more involved than address. It starts with btspp:// and ends with ;authenticate=false;encrypt=false;master=false
	 */
	public void connectToURL(String _url) {

		url = _url;
		
		try {
			// mainProgram.feedback(this, "connect to" + _url);
			connection = (StreamConnection) Connector.open(_url);
		} catch (IOException e) {
			mainProgram.feedback(this, "Sorry but could not connect. to " + _url);
			connected = false;
		}
			
		establishStreams();

	}
	
	void establishStreams(){
		connected = true;
		try {
			is = connection.openInputStream();
			os = connection.openOutputStream();

			// send((byte) 65);
		} catch (IOException e) {
			connected = false;
			mainProgram.feedback(this, "Couldn't make streams " + e);
		}
		//mainProgram.feedback(this,"tried to connect" + connected);
		if (connected  ) {
		// agent.cancelServiceSearch(whichServiceSearch);
			int startNumber = url.indexOf(":") + 3;
			int endNumber = url.indexOf(";", startNumber);
			String number = "12345";
			if (startNumber != -1 && endNumber != -1)
			number = url.substring(startNumber, endNumber);
			//System.out.println("Connected in BTSerialConnection");
			//mainProgram.feedback(this,"try connectionMade on " +  mainProgram + " for " + number + " " + getFriendlyName());

			
			if (mainProgram == null)connectionMade(this, number + " " + getFriendlyName());

			else mainProgram.connectionMade(this, number + " " + getFriendlyName());
		}

	}

	void doServiceDiscovery(RemoteDevice remote) {
		// mainForm.append("Searching for devices..\n");
		servicesFound = new Vector();
		serviceList = new Vector();
		mainProgram.feedback(this, "Looking for Services");
	
		
		try {
			int[] attrList = new int[1];
			attrList[0] = 0x100;
			UUID[] UUIDS = new UUID[] { new UUID(0x0003) };
			whichServiceSearch = agent.searchServices(attrList, UUIDS, remote, this); // direct discovery response to Listener object

		} catch (BluetoothStateException e) {
			e.printStackTrace();
			mainProgram.feedback(this, "Error in Asking for searchService" + e);
		}
	}

	public void servicesDiscovered(int param, ServiceRecord[] serviceRecord) {
		// mainProgram.blueToothFeedback( "Service discovered" + serviceRecord.length +"\n");
		// should only be one because I only asked for one
		// servicesFound.addElement(serviceRecord);
		// since I am only asking about one device
		// and I am only ask about one service, serial
		// this should only return once with one service
		//System.out.println("Service Discovered");
		for (int i = 0; i < serviceRecord.length; i++) {
			// mainProgram.blueToothFeedback( "Service discovered" + i);

			ServiceRecord r = serviceRecord[i];
										
			DataElement nameElement = r.getAttributeValue(0x100);
			//System.out.println("Service Part Discovered " + nameElement);
			String name = "";
			if (nameElement != null  && (nameElement.getDataType() == DataElement.STRING)) {
				name = (String) nameElement.getValue();
				// mainProgram.blueToothFeedback("Service Name:" + name + "\n");
				serviceList.addElement(name);
				servicesFound.addElement(r);
				System.out.println("Service added" + name);
			}

		}

	}

	public void serviceSearchCompleted(int _transID, int respCode) {
		// should do something here is I wanted to accomodate more services
		// spit back a list of services
		// for now just one service
		/*
		 * if (name.indexOf("Serial") != -1){ //serviceList.append(de.toString(),null); String url = r.getConnectionURL(0,false); // mainProgram.blueToothFeedback("URL:" + url); mainProgram.blueToothFeedback("Service Name:" + name + "\n"); mainProgram.blueToothFeedback("URL:" + url + "\n");
		 * 
		 * connectionOK = makeConnection(url); }
		 */
		// mainProgram.blueToothFeedback("service discovery completed with return code:"+complete);

			   String message = "no response code";
        if (respCode == DiscoveryListener.SERVICE_SEARCH_DEVICE_NOT_REACHABLE) { 
            message = "Device not reachable"; 
        } 
        else if (respCode == DiscoveryListener.SERVICE_SEARCH_NO_RECORDS) { 
            message = "Service not available"; 
        } 
        else if (respCode == DiscoveryListener.SERVICE_SEARCH_COMPLETED) { 
            message = "Service search completed"; 
        } 
        else if (respCode == DiscoveryListener.SERVICE_SEARCH_TERMINATED) { 
            message = "Service search terminated"; 
        } 
        else if (respCode == DiscoveryListener.SERVICE_SEARCH_ERROR) { 
            message = "Service search error"; 
        }        
    	System.out.println(message + " " + servicesFound.size()+" services are discovered");
          


		if (respCode == SERVICE_SEARCH_COMPLETED) {
			btProgram.servicesAvailable(serviceList);
		} else {
			mainProgram.feedback(this, "Couldn't find any services");

		}

	}

	/**
	 * Specifies which service of the ones discovered or the device already specified (pickDevice). Also initiates connection. You hear back about the connection with the connectionMade method.
	 */
	public void pickService(int _which) {

		ServiceRecord r = (ServiceRecord) servicesFound.elementAt(_which);
		String url = r.getConnectionURL(0, false);
		// mainProgram.blueToothFeedback("URL:" + url);
		// mainProgram.blueToothFeedback("Service Name:" + name + "\n");
		mainProgram.feedback(this, "URL:" + url + "\n");
		connectToURL(url);
	}

	public String getFriendlyName() {
		return friendlyName;
	}

	public void setFriendlyName(String _fn) {
		friendlyName = _fn;
	}

	/**
	 * Specifies which device of the ones discovered, you want to connect to. Also initiates the process of service disovery on the desired device. It takes some time. A BTConnectionListener will be alerted with a passing of a list of services to the funciton servicesAvailable when it finishes.
	 */
	public void pickDevice(int _which) {

		RemoteDevice rd = (RemoteDevice) devicesFound.elementAt(_which);
		try {
			friendlyName = rd.getFriendlyName(false);
			System.out.println("Friendly Picked " + friendlyName);
		} catch (IOException e) {
			mainProgram.feedback(this, "Prob" + e);
		}
		System.out.println(rd + "Service Discovery on " + friendlyName);

		doServiceDiscovery(rd);


		// System.out.println(rd + "Service Discovery on " + friendlyName);
	}

	/**
	 * Specifies which device of the ones discovered, you want to connect to. Also initiates the process of service disovery on the desired device. It takes some time. A BTConnectionListener will be alerted with a passing of a list of services to the funciton servicesAvailable when it finishes.
	 */
	public void connectToSerialServiceOnDevice(int _which) {

		RemoteDevice rd = (RemoteDevice) devicesFound.elementAt(_which);
		connectToHardwareAddress(rd.getBluetoothAddress() + ":01");

		// doServiceDiscovery(rd);

		try {
			friendlyName = rd.getFriendlyName(false);
		} catch (IOException e) {
			mainProgram.feedback(this, "Prob" + e);
		}
		// System.out.println(rd + "Service Discovery on " + friendlyName);
	}

	public void servicesAvailable(Vector _services) {
		// TODO Auto-generated method stub

	}

	public void devicesAvailable(Vector _devices) {
		// TODO Auto-generated method stub

	}
	
	public class BTServer implements Runnable {
		

		private boolean isReady = false;


		private StreamConnectionNotifier stream_connection_notifier;


		public BTServer() {
			Thread connectionThread = new Thread(this);
			connectionThread.start();

		}

		public void run() {
			String SERVICE = "Terminal";
			String UUID = "A1A2A3A4A5A6A7A8A9A0B1B2B3B4B5B6";


			try {
				LocalDevice.getLocalDevice().setDiscoverable(DiscoveryAgent.GIAC);
			} catch (Exception error) {
				error.printStackTrace();
			}

			try {
				stream_connection_notifier = (StreamConnectionNotifier) Connector.open("btspp://localhost:" + UUID + ";name=" + SERVICE);
			} catch (Exception error) {
				error.printStackTrace();
			}

			stopServer();

			isReady = true;

			try {
				while (isReady) {
					
					connection = stream_connection_notifier.acceptAndOpen();
					establishStreams();
					System.out.println("Client Connected");
				}
			} catch (Exception error) {
				error.printStackTrace();
			}
		}

		public void stopServer() {
			if (isReady) {
				isReady = false;
				try {
					stream_connection_notifier.close();
				} catch (Exception error) {
					error.printStackTrace();
				}
			}
		}
		
		
		/*
		 *
 String url = "btspp://localhost:2BBC2D287C8C11DBA1500040F45842EF;name=Checkers;authorize=false";
 //L2CAPConnectionNotifier service = (L2CAPConnectionNotifier)Connector.open(url);

		//	localDevice.getRecord(notifier);
	StreamConnectionNotifier service;
	try {
		service = (StreamConnectionNotifier) Connector.open(url);

		//ServiceRecord sr = local.getRecord(service);
		 
		StreamConnection connection =
		    (StreamConnection) service.acceptAndOpen();
		   byte buffer[] = new byte[100];
		    String msg = "hello there, client";
		    is = connection.openInputStream();
		    os = connection.openOutputStream();
		    // send data to the client
		    os.write(msg.getBytes());
		    // read data from client
		    is.read(buffer);
		    connection.close();
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
		//service.close();
		//notifier.acceptAndOpen();
		// BTServer server = new BTServer(mainProgram, waitForBytes);
		// server.run_server();
		// deleteAll();
		// append("Waiting for connection.");
		 */
	
	}

}
